package com.hzw.code.security.filter;

import com.alibaba.fastjson.JSON;
import com.hzw.code.common.constant.BaseConstant;
import com.hzw.code.common.utils.ActionException;
import com.hzw.code.common.utils.ActionResult;
import com.hzw.code.common.utils.JwtTokenUtils;
import com.hzw.code.common.utils.ResultCodeEnum;
import com.hzw.code.redis.dao.RedisDao;
import com.hzw.code.security.model.CustomUserDetails;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.stereotype.Component;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.UUID;

/**
 * @author: 胡汉三
 * @date: 2020/5/26 10:16
 * @description: 进行用户账号的验证==>认证功能
 *
 */
public class LoginAuthFilter extends UsernamePasswordAuthenticationFilter {

    private AuthenticationManager authenticationManager;
    private RedisDao<String,String> tokenRedis;
    private RedisDao<String, CustomUserDetails> userRedis;

    public LoginAuthFilter(AuthenticationManager authenticationManager,RedisDao<String,String> tokenRedis,RedisDao<String, CustomUserDetails> userRedis) {
        this.authenticationManager = authenticationManager;
        this.tokenRedis = tokenRedis;
        this.userRedis = userRedis;

        // 设置该过滤器地址
        // super.setFilterProcessesUrl("/jwt/login");
    }

    /**
     * description: 登录验证
     *
     * @param request
     * @param response
     * @return org.springframework.security.core.Authentication
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request,
                                                HttpServletResponse response) throws AuthenticationException {
        String tokenHeader = request.getHeader(BaseConstant.TOKEN_HEADER);
        // 如果请求头中没有Authorization信息则直接放行了
        if (tokenHeader == null || !tokenHeader.startsWith(BaseConstant.TOKEN_PREFIX)) {
            return authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(request.getParameter("username"),
                            request.getParameter("password"), new ArrayList<>())
            );
        }
        return getAuthentication(tokenHeader);
    }

    /**
     * description: 登录验证成功后调用，验证成功后将生成Token，并重定向到用户主页home
     * 与AuthenticationSuccessHandler作用相同
     *
     * @param request
     * @param response
     * @param chain
     * @param authResult
     * @return void
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest request,
                                            HttpServletResponse response,
                                            FilterChain chain,
                                            Authentication authResult) throws IOException, ServletException {

        // 查看源代码会发现调用getPrincipal()方法会返回一个实现了`UserDetails`接口的对象，这里是CustomUserDetails
        CustomUserDetails user = (CustomUserDetails) authResult.getPrincipal();
        String userKey = BaseConstant.USER_KEY + user.getUsername() + ":" + user.getUuidKey();
        // 多生成一个KEY，用于返回给前端，确保用户信息不暴露出去
        String tokenKey = UUID.randomUUID().toString() + ":" + UUID.randomUUID().toString();
        // 存储后，可以通过tokenKey，拿到userKey，在通过userKey拿到用户的信息
        tokenRedis.setKey(BaseConstant.TOKEN_KEY + tokenKey,userKey,null);

        // 登录成功
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        ActionResult<String> result = new ActionResult<>(ResultCodeEnum.SUCCESS);
        result.setMessage("登录成功");
        result.setData(tokenKey);
        response.getWriter().write(JSON.toJSONString(result));
    }

    /**
     * description: 读取Token信息，创建UsernamePasswordAuthenticationToken对象
     *
     * @param tokenHeader
     * @return org.springframework.security.authentication.UsernamePasswordAuthenticationToken
     */
    private UsernamePasswordAuthenticationToken getAuthentication(String tokenHeader) {
        //解析Token时将“Bearer ”前缀去掉
        String token = tokenHeader.replace(BaseConstant.TOKEN_PREFIX, "");
        String userKey = tokenRedis.getValue(BaseConstant.TOKEN_KEY + token);
        if(StringUtils.isBlank(userKey)){
            throw new ActionException("token无效");
        }
        CustomUserDetails userDetails = userRedis.getValue(userKey);
        if (userDetails != null){
            // 刷新token
            tokenRedis.expire(BaseConstant.TOKEN_KEY + token,null);
            tokenRedis.expire(userKey,null);
            return new UsernamePasswordAuthenticationToken(userDetails.getUsername(), null, userDetails.getAuthorities());
        }
        return null;
    }

    /**
     * description: 登录验证失败后调用，这里直接Json返回，实际上可以重定向到错误界面等
     * 与AuthenticationFailureHandler作用相同
     *
     * @param request
     * @param response
     * @param failed
     * @return void
     */
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        ActionResult<String> result = new ActionResult<>(ResultCodeEnum.LOGIN_FAIL);
        response.getWriter().write(JSON.toJSONString(result));
    }
}